/*
 * Created on2022/6/11 18:22
 *
 * Copyright(c) YuZhou Big Data Laboratory CO.,Ltd, 2022.  All rights reserved.
 *
 *******************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ******************************************************************************/

package org.yzbdl.lanius.core.plugins.metastore;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;

import javax.persistence.*;

import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.core.plugins.Plugin;
import org.pentaho.di.core.plugins.PluginTypeInterface;
import org.pentaho.di.core.plugins.PluginUniqueCode;
import org.yzbdl.lanius.core.constant.LocalDir;
import org.yzbdl.lanius.core.metastore.BaseEntity;
import org.yzbdl.lanius.core.plugins.ClassLoaderManager;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.Getter;
import lombok.Setter;

/**
 * 核心对象树插件信息
 *
 * @author hs@yzbdl.ac.cn
 * @version 1.0
 * @date 2019/6/11 18:22
 */
@Entity
@Table(name = "ldr_plugin")
@Getter
@Setter
public class LdrPluginEntity extends BaseEntity implements PluginUniqueCode {

    private static LogChannelInterface log = new LogChannel("PLUGIN");

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    protected Integer id;

    /**
     * 插件ID, 例如excelInput, 与插件中的注解对应
     */
    @Column(name = "plugin_id")
    private String pluginId;

    /**
     * 插件图片, 例如images/c3accessoutput.png, 与插件中的注解对应
     */
    @Column(name = "image")
    private String image;

    /**
     * 名称
     */
    @Column(name = "name")
    private String name;

    /**
     * 描述
     */
    @Column(name = "profile")
    private String profile;

    /**
     * 收费类型： 0免费, 1收费
     */
    @Column(name = "charge_type")
    private Integer chargeType = 0;

    /**
     * 使用次数
     */
    @Column(name = "usage_count")
    private Integer usageCount = 0;

    /**
     * 是否合法可用
     */
    @Column(name = "is_valid")
    private Boolean isValid = true;

    /**
     * 创建时间
     */
    @Column(name = "create_time")
    private Date createTime = new Date();

    /**
     * 类别描述
     */
    @Column(name = "category")
    private String category = "";

    /**
     * 国际化包名
     */
    @Column(name = "i18n_package_name")
    private String i18nPackageName = "";

    /**
     * 插件版本号
     */
    @Column(name = "version")
    private String version = "none";

    /**
     * 插件类别, 记录PluginType类的全名, 例如:org.pentaho.di.core.plugins.StepPluginType
     */
    @Column(name = "plugin_type_name")
    private String pluginTypeName;

    /**
     * 插件类别, 记录插件注解类的全名, 例如:org.pentaho.di.core.annotations.Step
     */
    @Column(name = "plugin_annotation_type_name")
    private String pluginAnnotationTypeName;

    /**
     * 主类, 插件入口, 一个插件只能存在一个, 例如:
     * org.pentaho.di.trans.steps.tableinput.TableInputMeta
     */
    @Column(name = "main_class_name")
    private String mainClassName;

    /**
     * 插件mainClassType类名, mainClass将实现该接口,
     * 例如：org.pentaho.di.trans.step.StepMetaInterface
     */
    @Column(name = "main_class_type_name")
    private String mainClassTypeName;

    /**
     * 是否本地插件, 默认false
     */
    @Column(name = "is_native_plugin")
    private Boolean isNativePlugin = false;

    /**
     * 是否每次加载都需要一个单独的类加载器
     */
    @Column(name = "cl_separate_class_loader_needed")
    private Boolean clSeparateClassLoaderNeeded = false;

    /**
     * 是否基于用户自定义组名进行类加载器分组, 如果有两个插件的clUserDefinedGroup相同, 那么他们将共用同一个类加载器, 实现类的相互调用。
     */
    @Column(name = "cl_user_defined_group")
    private String clUserDefinedGroup = "";

    /**
     * 是否基于插件目录进行类加载器分组, 以此确保同一个目录下的不同插件使用同一个类加载器, 实现类的相互调用。
     */
    @Column(name = "cl_folder_group")
    private Boolean clFolderGroup = false;

    /**
     * 依赖包名称，逗号分隔。
     */
    @Column(name = "library", length = 5000)
    private String library = "";

    /**
     * 商业版插件标识
     */
    @Column(name = "enterprise")
    private Boolean enterprise = false;

    /**
     * 扩展信息json
     */
    @Column(name = "meta_info", length = 10000)
    private String metaInfo = "";

    /**
     * 安装输出信息
     */
    @Transient
    private String installOutPrint;

    // /**
    // * 本次打开客户端使用次数
    // */
    // @Column(name = "usage_this_open")
    // private Integer usageThisOpen = 0;

    // @Transient
    // private Class<?> mainClass;

    // @Transient
    // private Annotation annotation;

    public LdrPluginEntity() {}

    public LdrPluginEntity(Integer id) {
        this.id = id;
    }

    /**
     * 获取插件类名简称, 例如StepPluginType
     *
     * @return
     */
    public String getPluginTypeShortName() {
        String[] split = pluginTypeName.split("\\.");
        return split[split.length - 1];
    }

    /**
     * 获取插件类别简称, 例如Step
     *
     * @return
     */
    public String getAnnotationTypeShortName() {
        String[] split = pluginAnnotationTypeName.split("\\.");
        return split[split.length - 1];
    }

    /**
     * 获取插件类名全称, 例如org.pentaho.di.core.plugins.StepPluginType
     *
     * @return
     */
    public String getPluginTypeName() {
        return this.pluginTypeName;
    }

    /**
     * 获取插件类
     *
     * @return
     */
    public Class<? extends PluginTypeInterface> getPluginTypeClass() {
        return ClassUtil.loadClass(this.pluginTypeName);
    }

    /**
     * 获取插件注解类 eg: org.pentaho.di.core.annotations.Step
     *
     * @return
     */
    public Class<? extends Annotation> getPluginAnnotationTypeClass() {
        return ClassUtil.loadClass(this.pluginAnnotationTypeName);
    }

    /**
     * 获取插件类实例
     */
    public PluginTypeInterface getPluginTypeInstance() {
        try {
            Class<? extends PluginTypeInterface> pluginTypeClass = getPluginTypeClass();
            Method method = ReflectUtil.getMethod(pluginTypeClass, "getInstance");
            PluginTypeInterface pluginTypeInterface = (PluginTypeInterface)ReflectUtil.invoke(null, method);
            return pluginTypeInterface;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取插件主类， 也即是meta类
     *
     * @return
     */
    public Class<?> getMainClass() {
        Class<?> clazz = null;
        try {
            // 避免使用Plugin.from()方法, 该方法内部调用了此方法, 会形成循环调用造成内存栈溢出.
            clazz = ClassLoaderManager.getInstance().getClassLoader(Plugin.fromWithOutClassMap(this))
                .loadClass(this.getMainClassName());
        } catch (ClassNotFoundException e) {
            log.logDebug("JAR包中没有找到类：" + e.getMessage());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return clazz;
    }

    /**
     * 获取插件主类的类别类， 也即是meta类实现的接口类
     *
     * @return
     */
    public Class<?> getMainClassTypeClass() {
        return ClassUtil.loadClass(this.getMainClassTypeName());
    }

    /**
     * 获取插件目录
     *
     * @return
     */
    public String getFolderName() {
        return this.getPluginId() + LocalDir.separator + this.getVersion();
    }

    /**
     * 获取依赖包名称数组
     *
     * @return
     */
    public String[] getLibraryArray() {
        return Optional.of(this.getLibrary()).orElse("").split(",");
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        LdrPluginEntity that = (LdrPluginEntity)o;
        return id.equals(that.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public Class<? extends PluginTypeInterface> getPluginType() {
        return this.getPluginTypeClass();
    }

}
